{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "48e9e2eb-4c5f-4f88-aae8-1e6f66798aa5",
   "metadata": {},
   "source": [
    "## PortQry Log Summariser\n",
    "\n",
    "This notebook uses a Large Language Model (LLM) to analyse PortQry firewall test log files.  \n",
    "Given a raw PortQry log, the LLM:\n",
    "\n",
    "- Parses each tested host, port, protocol, and status (LISTENING, NOT LISTENING, FILTERED, NO RESPONSE).\n",
    "- Classifies each check as a passed or failed firewall rule.\n",
    "- Generates a human-readable markdown summary of the results.\n",
    "- Produces CSV output for further analysis or reporting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85141dcd-7ce9-466e-9be4-d0f98123e992",
   "metadata": {},
   "outputs": [],
   "source": [
    "#imports \n",
    "\n",
    "import os\n",
    "from dotenv import load_dotenv\n",
    "from IPython.display import Markdown, display\n",
    "from openai import OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d630f22c-3eeb-4ee4-b895-7aeb8b80439a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load environment variables in a file called .env\n",
    "\n",
    "load_dotenv(override=True)\n",
    "api_key = os.getenv('OPENAI_API_KEY')\n",
    "\n",
    "# Check the key\n",
    "\n",
    "if not api_key:\n",
    "    print(\"No API key was found - please head over to the troubleshooting notebook in this folder to identify & fix!\")\n",
    "elif not api_key.startswith(\"sk-proj-\"):\n",
    "    print(\"An API key was found, but it doesn't start sk-proj-; please check you're using the right key - see troubleshooting notebook\")\n",
    "elif api_key.strip() != api_key:\n",
    "    print(\"An API key was found, but it looks like it might have space or tab characters at the start or end - please remove them - see troubleshooting notebook\")\n",
    "else:\n",
    "    print(\"API key found and looks good so far!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4011e7e4-3498-4fc8-aef8-63d0fbcc566c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reading and display portqry file contents\n",
    "log_filename = \"portqry_results.log\"\n",
    "\n",
    "# Read the file content into a string\n",
    "with open(log_filename, \"r\", encoding=\"utf-8\") as f:\n",
    "    log_contents = f.read()\n",
    "\n",
    "print(log_contents)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d527edcb-dc7b-4f16-a91e-b47fa030fd79",
   "metadata": {},
   "outputs": [],
   "source": [
    "# system and user prompts \n",
    "\n",
    "system_prompt = \"\"\"\n",
    "You are a highly reliable assistant that analyzes firewall-test log files generated using PortQry.\n",
    "\n",
    "Your responsibilities:\n",
    "\n",
    "1. Parse the log contents accurately, identifying:\n",
    "   - target host\n",
    "   - each tested port\n",
    "   - protocol (TCP/UDP)\n",
    "   - PortQry status (LISTENING, NOT LISTENING, FILTERED, or NO RESPONSE)\n",
    "\n",
    "2. Classify firewall rule outcomes:\n",
    "   - Passed → LISTENING\n",
    "   - Failed → NOT LISTENING, FILTERED, or NO RESPONSE\n",
    "\n",
    "3. Generate two outputs:\n",
    "   - A markdown summary of the results.\n",
    "   - A CSV file with passed/failed results (Passed Rules, Failed Rules) with the following columns:\n",
    "         Target,IP,Port,Protocol,Status(LISTENING/NOT LISTENING/FILTERED/NO RESPONSE),Result(Passed/Failed)\n",
    "     Please ensure that the CSV output matches the CSV format with each rule in a separate line. \n",
    "\n",
    "4. Ensure the analysis is accurate, structured, and suitable for automated processing.\n",
    "\n",
    "5. Please add the following headers in the response content:\n",
    "   -> ---SUMMARY_START--- and ---SUMMARY_END--- to wrap summary text.\n",
    "   -> ---CSV_START--- and ---CSV_END--- to wrap CSV contents in the response.\n",
    "\"\"\"\n",
    "\n",
    "user_prompt = \"\"\"\n",
    "Below is a PortQry firewall-test results log file.\n",
    "Please analyze all tested ports according to the rules defined in the system prompt and generate the required output files:\n",
    "\n",
    "1. Markdown summary.\n",
    "2. CSV file passed/failed results.\n",
    "\n",
    "Log contents:\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e568ea97-4332-4655-89ac-6c3b6c0c2fa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = [\n",
    "    {\"role\":\"system\", \"content\":system_prompt},\n",
    "    {\"role\":\"user\", \"content\":user_prompt + log_contents}\n",
    "           ] \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b4be6fa-28b8-4af4-87ca-f3eda8e633bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "from datetime import datetime\n",
    "\n",
    "openai = OpenAI()\n",
    "\n",
    "response = openai.chat.completions.create(model=\"gpt-5-nano\", messages=messages)\n",
    "response_text = response.choices[0].message.content\n",
    "display(Markdown(response_text))\n",
    "\n",
    "summary = response_text.split(\"---SUMMARY_START---\")[1].split(\"---SUMMARY_END---\")[0].strip()\n",
    "csv_data = response_text.split(\"---CSV_START---\")[1].split(\"---CSV_END---\")[0].strip()\n",
    "\n",
    "def write_with_backup(path: Path, content: str, encoding: str = \"utf-8\") -> None:\n",
    "    \"\"\"\n",
    "    If `path` exists, rename it to a timestamped backup, then write new content.\n",
    "    \"\"\"\n",
    "    if path.exists():\n",
    "        timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n",
    "        backup_path = path.with_name(f\"{path.stem}_{timestamp}{path.suffix}.bak\")\n",
    "        path.rename(backup_path)\n",
    "        print(f\"Backed up existing file to: {backup_path}\")\n",
    "\n",
    "    path.write_text(content, encoding=encoding)\n",
    "    print(f\"Wrote new file: {path}\")\n",
    "\n",
    "# current folder; change if needed\n",
    "base_dir = Path(\".\")\n",
    "\n",
    "md_path = base_dir / \"portqry_results_summary.md\"\n",
    "csv_path = base_dir / \"portqry_results_output.csv\"\n",
    "\n",
    "write_with_backup(md_path, summary)\n",
    "write_with_backup(csv_path, csv_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c57f535c-8791-48e6-899c-5f29aa7773c6",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
